/*
 
 RFA Manager, Refractor2 Archive editor.
 Copyright (C) 2008
 Torsten Kammer         torsten@ferroequinologist.de
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 */

#import "RFAArchive.h"
#import "NSDataRFAAdditions.h"
#import "RFADecompression.h"
#import "NSStringRFAAdditions.h"
#import "PropertyCategories.h"
#import "OpenDDSFile.h"
#import "DDSDrawToImage.h"

@implementation RFAArchive

- (id)initWithData:(NSData *)data error:(NSError **)error
{
    if (![super init]) return nil;
    
    files = [NSMutableDictionary dictionary];
    BOOL oldFormat = NO;
    char firstFewBytes[28];
    [data getBytes:firstFewBytes length:28];
    if (strncmp(firstFewBytes, "Refractor2 FlatArchive 1.1  ", 28) == 0)
        oldFormat = YES;
    
    unsigned int recordOffset = [data littleIntAtPosition:oldFormat ? 28 : 0];
    unsigned int isCompressed = [data littleIntAtPosition:oldFormat ? 32 : 4];
    
//    if (isCompressed && NSHostByteOrder() == NS_BigEndian)
//    {
//        if (error)
//            *error = [NSError errorWithDomain:@"unpack" code:2 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Cannot unpack on PowerPC", @"Trying to open compressed file on PowerPC, Description"), NSLocalizedDescriptionKey,
//                                                                         NSLocalizedString(@"RFA Manager cannot open compressed RFA files on PowerPC", @"Trying to open compressed file on PowerPC, Reason"), NSLocalizedFailureReasonErrorKey,
//                                                                         nil]];
//        return nil;
//    }
    
    if (recordOffset > data.length)
    {
        if (error)
            *error = [NSError errorWithDomain:@"unpack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Internal index is out of bounds", @"Index out of bounds when unpacking, Description"), NSLocalizedDescriptionKey,
                                                                         NSLocalizedString(@"A part of the file tells the app to look for data at places that do not exist.", @"Index out of bounds when unpacking, Reason"), NSLocalizedFailureReasonErrorKey,
                                                                         nil]];
        return nil;
    }
    
    unsigned int recordCount = [data littleIntAtPosition:recordOffset];
    files = [NSMutableArray arrayWithCapacity:recordCount];
    NSUInteger i;
    NSUInteger offset = recordOffset + 4;
    for (i = 0; i < recordCount; i++)
    {
        unsigned int nameLength = [data littleIntAtPosition:offset]; offset += 4;
        char *cName = malloc(nameLength + 1);
        [data getBytes:cName range:NSMakeRange(offset, nameLength)]; offset += nameLength;
        cName[nameLength] = 0; // Zero termination
        NSString *fileName = [NSString stringWithCString:cName encoding:NSASCIIStringEncoding];
        free(cName);
        
        unsigned int encodedSize = [data littleIntAtPosition:offset]; offset += 4;
        unsigned int normalSize = [data littleIntAtPosition:offset]; offset += 4;
        unsigned int position = [data littleIntAtPosition:offset]; offset += 4;
        
        if (position > data.length || (position + encodedSize) > data.length || (position + encodedSize) < position)
        {
            if (error)
                *error = [NSError errorWithDomain:@"unpack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Internal index is out of bounds", @"Index out of bounds when unpacking, Description"), NSLocalizedDescriptionKey,
                                                                             NSLocalizedString(@"A part of the file tells the app to look for data at places that do not exist.", @"Index out of bounds when unpacking, Reason"), NSLocalizedFailureReasonErrorKey,
                                                                             nil]];
			return nil;
        }
        
        offset += 12;
        
        if (offset > data.length)
        {
            if (error)
                *error = [NSError errorWithDomain:@"unpack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Internal index is out of bounds", @"Index out of bounds when unpacking, Description"), NSLocalizedDescriptionKey,
                                                                             NSLocalizedString(@"A part of the file tells the app to look for data at places that do not exist.", @"Index out of bounds when unpacking, Reason"), NSLocalizedFailureReasonErrorKey,
                                                                             nil]];
			return nil;
        }
        
        NSData *fileData = [data subdataWithRange:NSMakeRange(position, encodedSize)];
        if (isCompressed)
            fileData = [[self class] _decompressedDataFromData:fileData decompressedLength:normalSize];
        
        if (!fileData)
        {
            if (error)
                *error = [NSError errorWithDomain:@"unpack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Some error while decompressing", @"Unspecific error when decompressing, Description"), NSLocalizedDescriptionKey,
                                                                             NSLocalizedString(@"The file is compressed and there was an error when trying to chacnge that.", @"Unspecific error when decompressing, Reason"), NSLocalizedFailureReasonErrorKey,
                                                                             nil]];
            return nil;
        }
        [files addObject:[[RFAArchiveEntry alloc] initWithPath:fileName data:fileData]];
    }
    
    return self;
}

- (NSData *)completeFileData
{
    NSMutableData *rfaData = [NSMutableData data];
    [rfaData appendLittleInt:0]; // Offset to records, will get filled in later.
    [rfaData appendLittleInt:0]; // Uncompressed file format
    
    // Not sure what this doeas, but it doesn't work without.
    const char magicHeader[] = { 0x63, 0xEC, 0x95, 0xBF, 0xFE, 0x7B, 0x09, 0x3C, 0x3A, 0xF0, 0x72, 0xEE, 0xA4, 0x72, 0xE7, 0xD9, 0x3F, 0xCC, 0x99, 0xC0,
        0xD3, 0x71, 0xC1, 0x46, 0x89, 0xBD, 0xD7, 0x53, 0xB5, 0x7E, 0x05, 0xB9, 0xF3, 0xB3, 0xDB, 0x18, 0x75, 0x94, 0x44, 0xFF,
        0x9B, 0xD2, 0xB9, 0x53, 0xC4, 0x1F, 0xB4, 0xF5, 0x65, 0xF1, 0x68, 0x9F, 0x58, 0x83, 0xAF, 0x0F, 0x76, 0x1D, 0x44, 0x68,
        0x67, 0x96, 0x32, 0xD5, 0xB9, 0x17, 0x8C, 0x6F, 0x30, 0x21, 0x5F, 0x61, 0x5D, 0xD2, 0xE5, 0x49, 0x72, 0x64, 0xFB, 0xE2,
        0x55, 0xF6, 0xD5, 0xE1, 0xF3, 0x8F, 0xF1, 0x1C, 0xD7, 0x60, 0x49, 0xF1, 0xFB, 0x49, 0xCD, 0xE6, 0xDE, 0x9F, 0x10, 0x8D,
        0xD6, 0x2D, 0x42, 0xAB, 0xA8, 0x78, 0x5E, 0x98, 0x56, 0x48, 0xA4, 0xE9, 0x38, 0x63, 0x4A, 0x4D, 0x4E, 0x9C, 0x6F, 0xB5,
        0xD5, 0x0C, 0x50, 0xB8, 0x18, 0xA0, 0xBE, 0x35, 0x89, 0xD4, 0xD0, 0x3A, 0x10, 0xBD, 0xD5, 0x24, 0xA3, 0x4D, 0x8C, 0x08,
        0x17, 0xD3, 0x98, 0x00, 0x4B, 0xD0, 0x12, 0x48 };
    [rfaData appendBytes:magicHeader length:sizeof(magicHeader)];
    
    NSMutableData *recordData = [NSMutableData data];
    [recordData appendLittleInt:files.count];
    
    for (RFAArchiveEntry *file in files)
    {
        [recordData appendLittleInt:file.path.length];
        [recordData appendData:[file.path dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
        
        [recordData appendLittleInt:file.data.length];
        [recordData appendLittleInt:file.data.length];
        [recordData appendLittleInt:rfaData.length];

        [rfaData appendData:file.data];
        
		char magicData[] = {0xE0, 0x21, 0x13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        [recordData appendBytes:magicData length:12];
    }
    
    [rfaData replaceBytesInRange:NSMakeRange(0, 4) withLittleInt:rfaData.length];
    [rfaData appendData:recordData];
	[rfaData appendLittleInt:0];
    
    return rfaData;
}

- (NSMutableArray *)files;
{
    return files;
}

- (RFAArchiveEntry *)archiveEntryWithPath:(NSString *)path
{
	for (RFAArchiveEntry *entry in self.files)
	{
		if ([entry.path isEqual:path]) return entry;
	}
	return nil;
}

@end

@implementation RFAArchiveEntry

+ (void)initialize
{
	static BOOL initialized = NO;
	if (initialized) return;
	
	initialized = YES;
	[self setKeys:[NSArray arrayWithObject:@"contents"] triggerChangeNotificationsForDependentKey:@"data"];
	[self setKeys:[NSArray arrayWithObject:@"data"] triggerChangeNotificationsForDependentKey:@"contents"];
	[self setKeys:[NSArray arrayWithObjects:@"contents", @"data", nil] triggerChangeNotificationsForDependentKey:@"filesize"];
}

+ (NSArray *)extensionsToTreatAsText;
{
    return [NSArray arrayWithObjects:@"con", @"rs", @"ssc", nil];
}

+ (NSArray *)extensionsToTreatAsImage;
{
    return [NSArray arrayWithObjects:@"dds", @"tga", @"jpg", @"bmp", @"raw", nil];
}

- (id)initWithPath:(NSString *)aPath data:(NSData *)aData;
{
    if (![super init]) return nil;
    self.path = aPath;
    self.data = aData;
    return self;
}

@synthesize path;
@synthesize data;

@dynamic contents;
- (NSString *)contents
{
    if ([[[self class] extensionsToTreatAsText] containsObject:[self.path pathExtension]])
        return [NSString stringWithData:self.data encoding:NSISOLatin1StringEncoding];
    else return nil;
}
- (void)setContents:(NSString *)newContents
{
    [self willChangeValueForKey:@"contents"];
    
    self.data = [newContents dataUsingEncoding:NSISOLatin1StringEncoding];
	
    [self didChangeValueForKey:@"contents"];
}

@dynamic image;
- (NSImage *)image
{
	BOOL needsToSwap = NO;
    NSBitmapImageRep *bitmapImage;
    if ([self.path.pathExtension isEqual:@"dds"])
    {
        DDSFile *file = DDSOpenData((CFDataRef) self.data);
        if (!file) return nil;
        CFIndex width = DDSGetWidth(file);
        CFIndex height = DDSGetHeight(file);
        CGImageRef cgImage = DDSCreateImage(file, width, height);
        DDSDestroy(file);
        if (!cgImage) return nil;
        bitmapImage = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
        CGImageRelease(cgImage);
    }
    else if ([self.path.pathExtension isEqual:@"raw"])
    {
        BOOL is16Bit = YES;
        NSInteger edgeLength = (NSInteger) sqrtf((float) (self.data.length/2));
        if (edgeLength * edgeLength * 2 < self.data.length)
        {
            edgeLength = (NSInteger) sqrtf((float) (self.data.length));
            is16Bit = NO;
        }
        
        CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) self.data);
        if (!provider) return nil;
        CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
        CGImageRef cgImage = CGImageCreate(edgeLength, edgeLength, is16Bit ? 16 : 8, is16Bit ? 16 : 8, is16Bit ? 2*edgeLength : edgeLength, colorspace, is16Bit ? kCGBitmapByteOrder16Little : 0, provider, NULL, YES, kCGRenderingIntentDefault);
        CGColorSpaceRelease(colorspace);
        CGDataProviderRelease(provider);
        if (!cgImage) return nil;
        bitmapImage = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
        CGImageRelease(cgImage);
        needsToSwap = YES;
    }
    else
    {
        bitmapImage = [[NSBitmapImageRep alloc] initWithData:self.data];
    }
    
    if (!bitmapImage) return nil;
    
    NSImage *newImage = [[NSImage alloc] initWithSize:bitmapImage.size];
    [newImage addRepresentation:bitmapImage];
	newImage.flipped = needsToSwap;
    
	return newImage;
}

@dynamic filesize;
- (NSUInteger)filesize
{
    return data.length;
}

@dynamic imagesize;
- (NSNumber *)imagesize
{
    return nil;
}

@end
